import os
import re
import pandas as pd
import docx
import concurrent.futures
import logging
from tqdm import tqdm
import win32com.client
import pythoncom
import tempfile
import argparse
import sys
import time
import subprocess
import threading

# 配置日志
def setup_logging(log_file="report_parser.log"):
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),
            logging.StreamHandler()
        ]
    )

# 定义要提取的字段
FIELDS = [
    "应用名称", "应用包名", "MD5", "开发者", "版本信息", "应用商城", 
    "检测结果", "问题类型", "问题描述"
]

def convert_doc_to_docx(doc_path, max_retries=3):
    """将.doc格式转换为.docx格式，带重试机制"""
    for attempt in range(max_retries):
        word = None
        try:
            # 每次尝试都重新初始化COM组件
            pythoncom.CoInitialize()
            
            # 创建临时文件路径
            temp_dir = tempfile.gettempdir()
            docx_path = os.path.join(temp_dir, f"{os.path.basename(doc_path)}_attempt_{attempt}.docx")
            
            # 如果文件已存在，删除它
            if os.path.exists(docx_path):
                try:
                    os.remove(docx_path)
                except:
                    pass
            
            # 创建Word应用实例
            word = win32com.client.Dispatch("Word.Application")
            
            # 设置Word应用属性，使用try-except包装每个属性设置
            try:
                word.Visible = False
            except:
                logging.warning(f"无法设置Word.Visible属性，继续尝试转换")
            
            try:
                word.DisplayAlerts = False
            except:
                pass
            
            # 打开文档，使用绝对路径
            abs_doc_path = os.path.abspath(doc_path)
            doc = word.Documents.Open(abs_doc_path, ReadOnly=True)
            
            # 保存为.docx格式
            abs_docx_path = os.path.abspath(docx_path)
            doc.SaveAs2(abs_docx_path, FileFormat=16)  # 16代表.docx格式
            
            # 关闭文档
            doc.Close()
            
            # 验证文件是否成功创建
            if os.path.exists(docx_path) and os.path.getsize(docx_path) > 0:
                logging.info(f"成功转换文档: {os.path.basename(doc_path)} (尝试 {attempt + 1}/{max_retries})")
                return docx_path
            else:
                raise Exception("转换后的文件不存在或为空")
                
        except Exception as e:
            error_msg = str(e)
            logging.warning(f"转换文档 {os.path.basename(doc_path)} 第 {attempt + 1} 次尝试失败: {error_msg}")
            
            # 如果是最后一次尝试，记录错误
            if attempt == max_retries - 1:
                logging.error(f"转换文档 {doc_path} 失败，已尝试 {max_retries} 次: {error_msg}")
                return None
            
            # 等待一段时间再重试
            import time
            time.sleep(1)
            
        finally:
            # 确保Word应用被关闭
            try:
                if word:
                    word.Quit()
            except Exception as quit_error:
                logging.warning(f"关闭Word应用时出错: {quit_error}")
            
            # 释放COM组件
            try:
                pythoncom.CoUninitialize()
            except:
                pass
    
    return None

def convert_doc_to_docx_alternative(doc_path):
    """备用转换方法：使用单独的线程和进程"""
    try:
        # 创建临时文件路径
        temp_dir = tempfile.gettempdir()
        docx_path = os.path.join(temp_dir, f"{os.path.basename(doc_path)}_alt.docx")
        
        # 如果文件已存在，删除它
        if os.path.exists(docx_path):
            try:
                os.remove(docx_path)
            except:
                pass
        
        # 使用独立线程进行转换
        def convert_in_thread():
            word = None
            try:
                pythoncom.CoInitialize()
                word = win32com.client.Dispatch("Word.Application")
                word.Visible = False
                word.DisplayAlerts = False
                
                # 打开文档
                abs_doc_path = os.path.abspath(doc_path)
                doc = word.Documents.Open(abs_doc_path, ReadOnly=True)
                
                # 保存为.docx格式
                abs_docx_path = os.path.abspath(docx_path)
                doc.SaveAs2(abs_docx_path, FileFormat=16)
                
                doc.Close()
                
            except Exception as e:
                logging.error(f"备用转换方法失败: {e}")
                raise
            finally:
                try:
                    if word:
                        word.Quit()
                except:
                    pass
                try:
                    pythoncom.CoUninitialize()
                except:
                    pass
        
        # 在独立线程中执行转换
        thread = threading.Thread(target=convert_in_thread)
        thread.start()
        thread.join(timeout=60)  # 60秒超时
        
        if thread.is_alive():
            logging.error(f"备用转换方法超时: {doc_path}")
            return None
        
        # 验证文件是否成功创建
        if os.path.exists(docx_path) and os.path.getsize(docx_path) > 0:
            logging.info(f"备用方法成功转换文档: {os.path.basename(doc_path)}")
            return docx_path
        else:
            return None
            
    except Exception as e:
        logging.error(f"备用转换方法出错: {e}")
        return None

def find_section_paragraphs(doc, section_title):
    """找到指定章节的所有段落"""
    section_paragraphs = []
    found_section = False
    next_section_pattern = re.compile(r"^\d+\.\s")
    
    for i, para in enumerate(doc.paragraphs):
        text = para.text.strip()
        
        # 查找章节标题
        if section_title in text:
            found_section = True
            logging.debug(f"找到章节 '{section_title}' 在段落 {i}: {text}")
            continue
        
        # 如果找到章节，收集段落直到下一个章节
        if found_section:
            # 如果遇到下一个章节标题，则结束收集
            if next_section_pattern.match(text) and section_title not in text:
                logging.debug(f"在段落 {i} 处结束章节: {text}")
                break
            
            if text:  # 只添加非空段落
                section_paragraphs.append(para)
    
    if not found_section:
        logging.debug(f"未找到章节 '{section_title}'")
    else:
        logging.debug(f"章节 '{section_title}' 中找到 {len(section_paragraphs)} 个段落")
    
    return section_paragraphs

def extract_app_info_from_table(doc):
    """从表格中提取应用基本信息"""
    app_info = {field: "" for field in FIELDS[:6]}
    
    # 遍历所有表格
    for table in doc.tables:
        # 检查表格行数
        if len(table.rows) < 2:
            continue
            
        # 遍历表格行
        for row in table.rows:
            # 确保行至少有两个单元格
            if len(row.cells) < 2:
                continue
                
            # 获取第一个单元格的文本（可能是字段名）
            field_cell = row.cells[0].text.strip()
            # 获取第二个单元格的文本（可能是字段值）
            value_cell = row.cells[1].text.strip()
            
            # 匹配字段
            if "应用名称" in field_cell and not app_info["应用名称"]:
                app_info["应用名称"] = value_cell
                logging.debug(f"从表格中提取到应用名称: {value_cell}")
            elif "应用包名" in field_cell and not app_info["应用包名"]:
                app_info["应用包名"] = value_cell
                logging.debug(f"从表格中提取到应用包名: {value_cell}")
            elif "MD5" in field_cell and not app_info["MD5"]:
                app_info["MD5"] = value_cell
                logging.debug(f"从表格中提取到MD5: {value_cell}")
            elif "开发者" in field_cell and not app_info["开发者"]:
                app_info["开发者"] = value_cell
                logging.debug(f"从表格中提取到开发者: {value_cell}")
            elif "版本" in field_cell and not app_info["版本信息"]:
                app_info["版本信息"] = value_cell
                logging.debug(f"从表格中提取到版本信息: {value_cell}")
            elif "应用商城" in field_cell and not app_info["应用商城"]:
                app_info["应用商城"] = value_cell
                logging.debug(f"从表格中提取到应用商城: {value_cell}")
    
    return app_info

def extract_app_info(doc):
    """提取应用基本信息"""
    # 首先尝试从表格中提取信息
    app_info = extract_app_info_from_table(doc)
    
    # 如果表格中没有足够信息，再尝试从段落中提取
    if not all(app_info.values()):
        logging.debug("表格中未找到完整信息，尝试从段落中提取")
        
        # 获取应用基本信息章节的段落
        app_info_paragraphs = find_section_paragraphs(doc, "应用基本信息")
        
        # 定义正则表达式模式，用于提取信息
        patterns = {
            "应用名称": r"应用名称[：:]\s*(.*)",
            "应用包名": r"(?:应用包名|包名)[：:]\s*(.*)",
            "开发者": r"(?:开发者|开发商)[：:]\s*(.*)",
            "版本信息": r"版本[信息]?[：:]?\s*([\d\.]+|V\d+\.[\d\.]+)",
            "应用商城": r"(?:应用商城|应用市场)[：:]\s*(.*)"
        }
        
        # 遍历段落提取信息
        all_text = "\n".join([p.text for p in app_info_paragraphs])
        
        # 首先尝试从整个文本中提取
        for field, pattern in patterns.items():
            if not app_info[field]:  # 如果字段还没有值
                match = re.search(pattern, all_text)
                if match:
                    app_info[field] = match.group(1).strip()
                    logging.debug(f"从整个文本中提取到 {field}: {app_info[field]}")
        
        # 如果某些字段没有提取到，尝试逐段落提取
        for para in app_info_paragraphs:
            text = para.text.strip()
            for field, pattern in patterns.items():
                if not app_info[field]:  # 如果字段还没有值
                    match = re.search(pattern, text)
                    if match:
                        app_info[field] = match.group(1).strip()
                        logging.debug(f"从段落中提取到 {field}: {app_info[field]}")
    
    # 尝试从文件名提取应用名称（如果应用名称为空）
    if not app_info["应用名称"]:
        try:
            file_name = os.path.basename(doc.core_properties.title)
            if "(" in file_name:
                app_name_from_file = file_name.split("(")[0].strip()
                app_info["应用名称"] = app_name_from_file
                logging.debug(f"从文件名提取到应用名称: {app_name_from_file}")
        except:
            logging.debug("无法从文件名提取应用名称")
    
    return app_info

def extract_problem_types_from_tables(doc):
    """从侵害用户权益章节的表格中提取问题类型"""
    problem_types = []
    
    # 遍历所有表格，查找包含"问题类型"的表格
    table_count = 0
    for table in doc.tables:
        has_problem_type = False
        problem_type_value = ""
        
        for row in table.rows:
            if len(row.cells) < 2:
                continue
            
            field_cell = row.cells[0].text.strip()
            value_cell = row.cells[1].text.strip()
            
            if "问题类型" in field_cell and value_cell:
                has_problem_type = True
                problem_type_value = value_cell
                break
        
        if has_problem_type:
            table_count += 1
            # 只有当找到多个问题类型表格时，才添加序号
            if table_count > 1:
                problem_types.append(f"{table_count-1}. {problem_type_value}")
                logging.debug(f"从表格 {table_count} 中提取到问题类型(添加序号): {table_count-1}. {problem_type_value}")
            else:
                problem_types.append(f"{problem_type_value}")
                logging.debug(f"从表格 {table_count} 中提取到问题类型(无序号): {problem_type_value}")
    
    return "\n".join(problem_types) if problem_types else ""

def check_user_rights_violation(doc):
    """检查侵害用户权益问题章节，并提取问题描述"""
    result = {
        "检测结果": "通过",
        "问题类型": "",
        "问题描述": ""
    }
    
    # 尝试找到"（三）侵害用户权益问题："章节
    violation_paragraphs = []
    found_section = False
    
    for i, para in enumerate(doc.paragraphs):
        text = para.text.strip()
        
        # 查找"（三）侵害用户权益问题："章节
        if ("侵害用户权益问题" in text):
            found_section = True
            logging.debug(f"找到侵害用户权益问题章节: {text}")
            continue
        
        # 如果找到章节，收集段落直到下一个章节
        if found_section:
            # 如果遇到下一个章节标题，则结束收集
            if re.match(r"^[（\(]?[123456789]", text) or re.match(r"^\d+\.", text):
                result["问题描述"] = text
                logging.debug(f"在段落处结束侵害用户权益问题章节: {text}")
                break
            
            if text:  # 只添加非空段落
                violation_paragraphs.append(text)
    
    # 合并所有段落内容
    all_content = "\n".join(violation_paragraphs)
    logging.debug(f"侵害用户权益问题章节内容: {all_content}")
    
    # 检查内容是否为空，或者同时满足以"无"开头且以"未发现相关问题"结尾
    if (all_content.strip().lower().startswith("无")):
        result["检测结果"] = "通过"
        result["问题描述"] = ""  # 问题描述置为空
        result["问题类型"] = ""  # 问题类型置为空
        logging.debug("侵害用户权益问题为空或以'无'开头且以'未发现相关问题'结尾，检测结果为'通过'")
    else:
        result["检测结果"] = "不通过"  # 检测结果置为"不通过"
        # 提取侵害用户权益章节表格中的问题类型
        result["问题类型"] = extract_problem_types_from_tables(doc)
        logging.debug(f"检测结果为'不通过'，问题描述已设置，问题类型：{result['问题类型']}")
    
    return result

def process_document(file_path):
    """处理单个Word文档"""
    try:
        # 检查文件是否存在
        if not os.path.exists(file_path):
            logging.error(f"文件不存在: {file_path}")
            return {field: "" for field in FIELDS}
        
        logging.info(f"开始处理文件: {file_path}")
            
        # 检查文件类型
        temp_file = None
        if file_path.endswith('.doc'):
            # 如果是.doc格式，先转换为.docx
            temp_file = convert_doc_to_docx(file_path)
            if not temp_file:
                logging.warning(f"主转换方法失败，尝试备用方法: {file_path}")
                # 尝试备用转换方法
                temp_file = convert_doc_to_docx_alternative(file_path)
                if not temp_file:
                    logging.error(f"所有转换方法都失败，跳过文件: {file_path}")
                    return {field: "" for field in FIELDS}
            doc_path = temp_file
        else:
            doc_path = file_path
        
        # 打开文档
        doc = docx.Document(doc_path)
        
        # 记录文档基本信息
        logging.debug(f"文档有 {len(doc.paragraphs)} 个段落, {len(doc.tables)} 个表格")
        
        # 提取应用基本信息
        app_info = extract_app_info(doc)
        
        # 检查侵害用户权益问题章节
        rights_violation_info = check_user_rights_violation(doc)
        
        # 合并信息
        result = {**app_info, **rights_violation_info}
        
        # 清理临时文件
        if temp_file and os.path.exists(temp_file):
            try:
                os.remove(temp_file)
            except:
                pass
        
        logging.info(f"成功解析文件: {os.path.basename(file_path)}")
        logging.debug(f"解析结果: {result}")
        return result
    
    except Exception as e:
        logging.error(f"处理文件 {file_path} 时出错: {str(e)}")
        import traceback
        logging.error(traceback.format_exc())
        # 返回空结果
        return {field: "" for field in FIELDS}

def process_documents(directory, max_workers=2):
    """多线程处理目录下的所有Word文档"""
    if not os.path.exists(directory):
        logging.error(f"目录不存在: {directory}")
        return []
    
    # 获取所有Word文档
    doc_files = [os.path.join(directory, f) for f in os.listdir(directory) 
                if f.endswith('.doc') or f.endswith('.docx')]
    
    if not doc_files:
        logging.warning(f"在目录 {directory} 中未找到Word文档")
        return []
    
    logging.info(f"找到 {len(doc_files)} 个Word文档")
    results = []
    
    # 限制并发数以避免COM组件冲突，对于.doc文件使用更少的线程
    doc_count = sum(1 for f in doc_files if f.endswith('.doc'))
    if doc_count > 0:
        # 如果有.doc文件，使用更保守的线程数
        max_workers = min(max_workers, 2, len(doc_files))
    else:
        # 如果只有.docx文件，可以使用更多线程
        max_workers = min(max_workers, len(doc_files))
    
    logging.info(f"使用 {max_workers} 个线程进行处理 (其中 {doc_count} 个.doc文件)")
    
    # 分批处理，避免同时转换太多.doc文件
    batch_size = 5
    doc_batches = [doc_files[i:i + batch_size] for i in range(0, len(doc_files), batch_size)]
    
    for batch_idx, batch in enumerate(doc_batches):
        logging.info(f"处理第 {batch_idx + 1}/{len(doc_batches)} 批文档 ({len(batch)} 个文件)")
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            # 提交批次任务并显示进度条
            future_to_file = {executor.submit(process_document, file_path): file_path for file_path in batch}
            
            for future in tqdm(concurrent.futures.as_completed(future_to_file), 
                             total=len(batch), 
                             desc=f"批次 {batch_idx + 1}"):
                file_path = future_to_file[future]
                try:
                    result = future.result()
                    if result and any(result.values()):  # 只添加有效结果
                        results.append(result)
                except Exception as e:
                    logging.error(f"获取 {file_path} 的处理结果时出错: {str(e)}")
        
        # 批次间稍作等待，让系统稳定
        if batch_idx < len(doc_batches) - 1:
            time.sleep(2)
    
    return results

def main():
    """主函数"""
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='解析Word文档并提取应用信息到Excel')
    parser.add_argument('-i', '--input', type=str, required=True,
                        help='包含Word文档的目录路径')
    parser.add_argument('-o', '--output', type=str, required=True,
                        help='输出Excel文件路径')
    parser.add_argument('-t', '--threads', type=int, default=2,
                        help='处理文档的线程数 (建议1-2个，避免COM组件冲突)')
    parser.add_argument('-l', '--log', type=str, default="report_parser.log",
                        help='日志文件路径')
    
    args = parser.parse_args()
    
    # 设置日志
    setup_logging(args.log)
    
    logging.info(f"开始处理目录: {args.input}")
    logging.info(f"输出文件: {args.output}")
    logging.info(f"使用线程数: {args.threads}")
    
    try:
        # 处理文档
        results = process_documents(args.input, args.threads)
        
        if not results:
            logging.error("未能成功解析任何文档")
            return 1
        
        # 创建DataFrame并保存为Excel
        df = pd.DataFrame(results)
        
        # 确保所有列都存在
        for field in FIELDS:
            if field not in df.columns:
                df[field] = ""
        
        # 重新排序列
        df = df[FIELDS]
        
        # 确保输出目录存在
        output_dir = os.path.dirname(args.output)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        # 保存到Excel
        df.to_excel(args.output, index=False)
        logging.info(f"解析完成，结果已保存到: {args.output}")
        logging.info(f"共处理 {len(results)} 个文档")
        return 0
    
    except Exception as e:
        logging.error(f"处理过程中出错: {str(e)}")
        return 1

if __name__ == "__main__":
    sys.exit(main())
